// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// Many functions here are mostly camelcase, with an occasional underscore to
// separate phrases.
// ignore_for_file: non_constant_identifier_names

import 'dart:math' as math;

import 'package:_fe_analyzer_shared/src/parser/quote.dart'
    show analyzeQuote, Quote, firstQuoteLength, lastQuoteLength;
import 'package:_fe_analyzer_shared/src/scanner/characters.dart' as char;
import 'package:analysis_server/lsp_protocol/protocol.dart'
    show SemanticTokenTypes, SemanticTokenModifiers;
import 'package:analysis_server/src/lsp/constants.dart'
    show CustomSemanticTokenModifiers, CustomSemanticTokenTypes;
import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart'
    show SemanticTokenInfo;
import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart'
    show highlightRegionTokenModifiers, highlightRegionTokenTypes;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;

/// A computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a Dart [CompilationUnit].
class DartUnitHighlightsComputer {
  final CompilationUnit _unit;
  final SourceRange? range;

  final _regions = <HighlightRegion>[];
  final _semanticTokens = <SemanticTokenInfo>[];
  bool _computeRegions = false;
  bool _computeSemanticTokens = false;

  /// Creates a computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a
  /// Dart [CompilationUnit].
  ///
  /// If [range] is supplied, tokens outside of this range will not be included
  /// in results.
  DartUnitHighlightsComputer(this._unit, {this.range});

  /// Returns the computed highlight regions, not `null`.
  List<HighlightRegion> compute() {
    _reset();
    _computeRegions = true;
    _unit.accept(_DartUnitHighlightsComputerVisitor(this));
    _addCommentRanges();
    return _regions;
  }

  /// Returns the computed semantic tokens, not `null`.
  List<SemanticTokenInfo> computeSemanticTokens() {
    _reset();
    _computeSemanticTokens = true;
    _unit.accept(_DartUnitHighlightsComputerVisitor(this));
    _addCommentRanges();
    return _semanticTokens;
  }

  void _addCommentRanges() {
    Token? token = _unit.beginToken;
    while (token != null) {
      Token? commentToken = token.precedingComments;
      while (commentToken != null) {
        HighlightRegionType? highlightType;
        if (commentToken.type == TokenType.MULTI_LINE_COMMENT) {
          if (commentToken.lexeme.startsWith('/**')) {
            highlightType = HighlightRegionType.COMMENT_DOCUMENTATION;
          } else {
            highlightType = HighlightRegionType.COMMENT_BLOCK;
          }
        }
        if (commentToken.type == TokenType.SINGLE_LINE_COMMENT) {
          if (commentToken.lexeme.startsWith('///')) {
            highlightType = HighlightRegionType.COMMENT_DOCUMENTATION;
          } else {
            highlightType = HighlightRegionType.COMMENT_END_OF_LINE;
          }
        }
        if (highlightType != null) {
          _addRegion_token(commentToken, highlightType);
        }
        commentToken = commentToken.next;
      }
      if (token.isEof) {
        // Only exit the loop *after* processing the EOF token as it may
        // have preceding comments.
        break;
      }
      token = token.next;
    }
  }

  void _addIdentifierRegion({
    required AstNode parent,
    required Token nameToken,
    required Element2? element,
  }) {
    if (_addIdentifierRegion_keyword(nameToken)) {
      return;
    }
    if (_addIdentifierRegion_class(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_extension(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_constructor(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_getterSetterDeclaration(
      parent,
      nameToken,
      element,
    )) {
      return;
    }
    if (_addIdentifierRegion_field(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_function(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_importPrefix(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_label(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_localVariable(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_method(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_parameter(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_typeAlias(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_typeParameter(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_unresolvedInstanceMemberReference(
      parent,
      nameToken,
      element,
    )) {
      return;
    }
    _addRegion_token(nameToken, HighlightRegionType.IDENTIFIER_DEFAULT);
  }

  void _addIdentifierRegion_annotation(Annotation node) {
    var arguments = node.arguments;
    if (arguments == null) {
      _addRegion_node(node, HighlightRegionType.ANNOTATION);
    } else {
      _addRegion_nodeStart_tokenEnd(
        node,
        arguments.beginToken,
        HighlightRegionType.ANNOTATION,
      );
      _addRegion_token(arguments.endToken, HighlightRegionType.ANNOTATION);
    }
  }

  bool _addIdentifierRegion_class(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    if (element is! InterfaceElement2) {
      return false;
    }
    // prepare type
    HighlightRegionType type;
    SemanticTokenTypes? semanticType;
    Set<SemanticTokenModifiers>? semanticModifiers;
    var grandParent = parent.parent;
    if (parent is NamedType &&
        grandParent is ConstructorName &&
        grandParent.parent is InstanceCreationExpression) {
      // new Class()
      type = HighlightRegionType.CONSTRUCTOR;
      semanticType = SemanticTokenTypes.class_;
      semanticModifiers = {CustomSemanticTokenModifiers.constructor};
    } else if (element is EnumElement2) {
      type = HighlightRegionType.ENUM;
    } else if (element is ExtensionTypeElement2) {
      type = HighlightRegionType.EXTENSION_TYPE;
    } else {
      type = HighlightRegionType.CLASS;
      if (parent is ConstructorDeclaration) {
        semanticModifiers = {
          CustomSemanticTokenModifiers.constructor,
          SemanticTokenModifiers.declaration,
        };
      }
    }

    // add region
    return _addRegion_token(
      nameToken,
      type,
      semanticTokenType: semanticType,
      semanticTokenModifiers: semanticModifiers,
      additionalSemanticTokenModifiers:
          _isAnnotationIdentifier(parent)
              ? {CustomSemanticTokenModifiers.annotation}
              : null,
    );
  }

  bool _addIdentifierRegion_constructor(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    if (element is! ConstructorElement2) {
      return false;
    }
    return _addRegion_token(
      nameToken,
      HighlightRegionType.CONSTRUCTOR,
      // For semantic tokens, constructor names are coloured like methods but
      // have a modifier applied.
      semanticTokenType: SemanticTokenTypes.method,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        if (_isAnnotationIdentifier(parent))
          CustomSemanticTokenModifiers.annotation,
      },
    );
  }

  bool _addIdentifierRegion_extension(Token nameToken, Element2? element) {
    if (element is! ExtensionElement2) {
      return false;
    }

    return _addRegion_token(nameToken, HighlightRegionType.EXTENSION);
  }

  bool _addIdentifierRegion_field(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    // Compute the type of the identifier.
    HighlightRegionType? type;
    if (element is FieldElement2) {
      if (element.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_FIELD_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_FIELD_REFERENCE;
      }
    } else if (element is TopLevelVariableElement2) {
      type = HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION;
    } else if (element is GetterElement) {
      var accessor = element;
      var variable = accessor.variable3;
      if (variable is TopLevelVariableElement2) {
        type = HighlightRegionType.TOP_LEVEL_GETTER_REFERENCE;
      } else if (variable is FieldElement2 && variable.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (accessor.isStatic) {
        type = HighlightRegionType.STATIC_GETTER_REFERENCE;
      } else {
        type = HighlightRegionType.INSTANCE_GETTER_REFERENCE;
      }
    } else if (element is SetterElement) {
      var accessor = element;
      var variable = accessor.variable3;
      if (variable is TopLevelVariableElement2) {
        type = HighlightRegionType.TOP_LEVEL_SETTER_REFERENCE;
      } else if (variable is FieldElement2 && variable.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (accessor.isStatic) {
        type = HighlightRegionType.STATIC_SETTER_REFERENCE;
      } else {
        type = HighlightRegionType.INSTANCE_SETTER_REFERENCE;
      }
    } else if (element == null &&
        parent is PropertyAccess &&
        nameToken == parent.propertyName.token) {
      // Handle tokens that are references to record fields.
      var staticType = parent.realTarget.staticType;
      if (staticType is RecordType) {
        type =
            staticType.fieldByName(nameToken.lexeme) != null
                ? HighlightRegionType.INSTANCE_GETTER_REFERENCE
                : HighlightRegionType.UNRESOLVED_INSTANCE_MEMBER_REFERENCE;
      }
    }
    // Add the highlight region.
    if (type != null) {
      return _addRegion_token(
        nameToken,
        type,
        additionalSemanticTokenModifiers:
            _isAnnotationIdentifier(parent)
                ? {CustomSemanticTokenModifiers.annotation}
                : null,
      );
    }
    return false;
  }

  bool _addIdentifierRegion_function(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    if (element is! TopLevelFunctionElement &&
        element is! LocalFunctionElement) {
      return false;
    }
    var isInvocation =
        parent is MethodInvocation && parent.methodName.token == nameToken;
    HighlightRegionType type;
    var isTopLevel = element is TopLevelFunctionElement;
    type =
        isTopLevel
            ? isInvocation
                ? HighlightRegionType.TOP_LEVEL_FUNCTION_REFERENCE
                : HighlightRegionType.TOP_LEVEL_FUNCTION_TEAR_OFF
            : isInvocation
            ? HighlightRegionType.LOCAL_FUNCTION_REFERENCE
            : HighlightRegionType.LOCAL_FUNCTION_TEAR_OFF;
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_getterSetterDeclaration(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    // should be declaration
    if (!(parent is MethodDeclaration || parent is FunctionDeclaration)) {
      return false;
    }
    var isTopLevel = parent.parent is CompilationUnit;
    HighlightRegionType type;
    if (element is GetterElement) {
      if (isTopLevel) {
        type = HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_GETTER_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_GETTER_DECLARATION;
      }
    } else if (element is SetterElement) {
      if (isTopLevel) {
        type = HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_SETTER_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_SETTER_DECLARATION;
      }
    } else {
      // should be property accessor
      return false;
    }
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_importPrefix(Token nameToken, Element2? element) {
    if (element is! PrefixElement2) {
      return false;
    }
    return _addRegion_token(nameToken, HighlightRegionType.IMPORT_PREFIX);
  }

  bool _addIdentifierRegion_keyword(Token nameToken) {
    var name = nameToken.lexeme;
    if (name == 'void') {
      return _addRegion_token(
        nameToken,
        HighlightRegionType.KEYWORD,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.void_},
      );
    }
    return false;
  }

  bool _addIdentifierRegion_label(Token nameToken, Element2? element) {
    if (element is! LabelElement2) {
      return false;
    }
    return _addRegion_token(nameToken, HighlightRegionType.LABEL);
  }

  bool _addIdentifierRegion_localVariable(Token nameToken, Element2? element) {
    if (element is! LocalVariableElement2) {
      return false;
    }
    // OK
    var type =
        element.type is DynamicType
            ? HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_REFERENCE
            : HighlightRegionType.LOCAL_VARIABLE_REFERENCE;
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_method(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    if (element is! MethodElement2) {
      return false;
    }
    var isStatic = element.isStatic;
    var isInvocation =
        parent is MethodInvocation && parent.methodName.token == nameToken;
    // OK
    HighlightRegionType type;
    if (isStatic) {
      type =
          isInvocation
              ? HighlightRegionType.STATIC_METHOD_REFERENCE
              : HighlightRegionType.STATIC_METHOD_TEAR_OFF;
    } else {
      type =
          isInvocation
              ? HighlightRegionType.INSTANCE_METHOD_REFERENCE
              : HighlightRegionType.INSTANCE_METHOD_TEAR_OFF;
    }
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_parameter(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    if (element is! FormalParameterElement) {
      return false;
    }
    var type =
        element.type is DynamicType
            ? HighlightRegionType.DYNAMIC_PARAMETER_REFERENCE
            : HighlightRegionType.PARAMETER_REFERENCE;
    var modifiers =
        parent is Label ? {CustomSemanticTokenModifiers.label} : null;
    return _addRegion_token(nameToken, type, semanticTokenModifiers: modifiers);
  }

  bool _addIdentifierRegion_typeAlias(Token nameToken, Element2? element) {
    if (element is TypeAliasElement2) {
      var type =
          element.aliasedType is FunctionType
              ? HighlightRegionType.FUNCTION_TYPE_ALIAS
              : HighlightRegionType.TYPE_ALIAS;
      return _addRegion_token(nameToken, type);
    }
    return false;
  }

  bool _addIdentifierRegion_typeParameter(Token nameToken, Element2? element) {
    if (element is! TypeParameterElement2) {
      return false;
    }
    return _addRegion_token(
      nameToken,
      HighlightRegionType.TYPE_PARAMETER,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(element),
    );
  }

  bool _addIdentifierRegion_unresolvedInstanceMemberReference(
    AstNode parent,
    Token nameToken,
    Element2? element,
  ) {
    // unresolved
    if (element != null) {
      return false;
    }
    // invoke / get / set
    var decorate = false;
    if (parent is MethodInvocation) {
      var target = parent.realTarget;
      if (parent.methodName.token == nameToken &&
          target != null &&
          _isDynamicExpression(target)) {
        decorate = true;
      }
    } else {
      if (parent is PrefixedIdentifier) {
        decorate = parent.identifier.token == nameToken;
      } else if (parent is PropertyAccess) {
        decorate = parent.propertyName.token == nameToken;
      }
    }
    if (decorate) {
      _addRegion_token(
        nameToken,
        HighlightRegionType.UNRESOLVED_INSTANCE_MEMBER_REFERENCE,
      );
      return true;
    }
    return false;
  }

  /// Returns a set of additional semantic token modifiers that apply to
  /// [element].
  Set<SemanticTokenModifiers>? _additionalModifiersForElement(
    Element2? element,
  ) {
    return (element?.isWildcardVariable ?? false)
        ? {CustomSemanticTokenModifiers.wildcard}
        : null;
  }

  /// Adds a highlight region/semantic token for the given [offset]/[length].
  ///
  /// If [semanticTokenType] or [semanticTokenModifiers] are not provided, the
  /// values from the default LSP mapping for [type] (also used for plugins)
  /// will be used instead. If [additionalSemanticTokenModifiers] are provided,
  /// they will always be added in addition to [semanticTokenModifiers]/the
  /// defaults.
  ///
  /// If the computer has a [range] set, tokens that fall outside of that range
  /// will not be recorded.
  void _addRegion(
    int offset,
    int length,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
    Set<SemanticTokenModifiers>? additionalSemanticTokenModifiers,
  }) {
    var range = this.range;
    if (range != null) {
      var end = offset + length;
      // Skip token if it ends before the range of starts after the range.
      if (end < range.offset || offset > range.end) {
        return;
      }
    }
    if (_computeRegions) {
      _regions.add(HighlightRegion(type, offset, length));
    }
    if (_computeSemanticTokens) {
      // Use default mappings if an overridden type/modifiers were not supplied.
      semanticTokenType ??= highlightRegionTokenTypes[type];
      semanticTokenModifiers ??= highlightRegionTokenModifiers[type];

      if (additionalSemanticTokenModifiers != null &&
          additionalSemanticTokenModifiers.isNotEmpty) {
        semanticTokenModifiers = {
          ...?semanticTokenModifiers,
          ...additionalSemanticTokenModifiers,
        };
      }
      if (semanticTokenType != null) {
        _semanticTokens.add(
          SemanticTokenInfo(
            offset,
            length,
            semanticTokenType,
            semanticTokenModifiers,
          ),
        );
      }
    }
  }

  bool _addRegion_node(
    AstNode node,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
  }) {
    var offset = node.offset;
    var length = node.length;
    _addRegion(
      offset,
      length,
      type,
      semanticTokenType: semanticTokenType,
      semanticTokenModifiers: semanticTokenModifiers,
    );
    return true;
  }

  void _addRegion_nodeStart_tokenEnd(
    AstNode a,
    Token b,
    HighlightRegionType type,
  ) {
    var offset = a.offset;
    var end = b.end;
    _addRegion(offset, end - offset, type);
  }

  bool _addRegion_token(
    Token? token,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
    Set<SemanticTokenModifiers>? additionalSemanticTokenModifiers,
  }) {
    if (token != null) {
      var offset = token.offset;
      var length = token.length;
      _addRegion(
        offset,
        length,
        type,
        semanticTokenType: semanticTokenType,
        semanticTokenModifiers: semanticTokenModifiers,
        additionalSemanticTokenModifiers: additionalSemanticTokenModifiers,
      );
    }
    return true;
  }

  void _addRegion_tokenStart_tokenEnd(
    Token a,
    Token b,
    HighlightRegionType type,
  ) {
    var offset = a.offset;
    var end = b.end;
    _addRegion(offset, end - offset, type);
  }

  /// Checks whether [parent] is the identifier part of an annotation.
  bool _isAnnotationIdentifier(AstNode? parent) {
    if (parent is Annotation) {
      return true;
    } else if (parent is PrefixedIdentifier && parent.parent is Annotation) {
      return true;
    } else {
      return false;
    }
  }

  void _reset() {
    _computeRegions = false;
    _computeSemanticTokens = false;
    _regions.clear();
    _semanticTokens.clear();
  }

  static bool _isDynamicExpression(Expression e) {
    var type = e.staticType;
    return type != null && type is DynamicType || type is InvalidType;
  }
}

/// An AST visitor for [DartUnitHighlightsComputer].
class _DartUnitHighlightsComputerVisitor extends RecursiveAstVisitor<void> {
  final DartUnitHighlightsComputer computer;

  _DartUnitHighlightsComputerVisitor(this.computer);

  @override
  void visitAnnotation(Annotation node) {
    computer._addIdentifierRegion_annotation(node);
    super.visitAnnotation(node);
  }

  @override
  void visitAsExpression(AsExpression node) {
    computer._addRegion_token(node.asOperator, HighlightRegionType.BUILT_IN);
    super.visitAsExpression(node);
  }

  @override
  void visitAssertStatement(AssertStatement node) {
    computer._addRegion_token(
      node.assertKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitAssertStatement(node);
  }

  @override
  void visitAssignedVariablePattern(AssignedVariablePattern node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_REFERENCE,
    );
    super.visitAssignedVariablePattern(node);
  }

  @override
  void visitAugmentedExpression(AugmentedExpression node) {
    computer._addRegion_token(
      node.augmentedKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitAugmentedExpression(node);
  }

  @override
  void visitAugmentedInvocation(AugmentedInvocation node) {
    computer._addRegion_token(
      node.augmentedKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitAugmentedInvocation(node);
  }

  @override
  void visitAwaitExpression(AwaitExpression node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.BUILT_IN,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitAwaitExpression(node);
  }

  @override
  void visitBlockFunctionBody(BlockFunctionBody node) {
    _addRegions_functionBody(node);
    super.visitBlockFunctionBody(node);
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.KEYWORD);
    computer._addRegion_node(node, HighlightRegionType.LITERAL_BOOLEAN);
    super.visitBooleanLiteral(node);
  }

  @override
  void visitBreakStatement(BreakStatement node) {
    computer._addRegion_token(
      node.breakKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitBreakStatement(node);
  }

  @override
  void visitCaseClause(CaseClause node) {
    computer._addRegion_token(
      node.caseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitCaseClause(node);
  }

  @override
  void visitCastPattern(CastPattern node) {
    computer._addRegion_token(node.asToken, HighlightRegionType.BUILT_IN);
    super.visitCastPattern(node);
  }

  @override
  void visitCatchClause(CatchClause node) {
    computer._addRegion_token(
      node.catchKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.onKeyword,
      HighlightRegionType.BUILT_IN,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );

    var exceptionParameter = node.exceptionParameter;
    if (exceptionParameter != null) {
      computer._addRegion_token(
        exceptionParameter.name,
        HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          exceptionParameter.declaredElement2,
        ),
      );
    }
    var stackTraceParameter = node.stackTraceParameter;
    if (stackTraceParameter != null) {
      computer._addRegion_token(
        stackTraceParameter.name,
        HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          stackTraceParameter.declaredElement2,
        ),
      );
    }

    super.visitCatchClause(node);
  }

  @override
  void visitClassDeclaration(ClassDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.sealedKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(
      node.interfaceKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.finalKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.classKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.name,
      HighlightRegionType.CLASS,
      semanticTokenModifiers: {SemanticTokenModifiers.declaration},
    );
    super.visitClassDeclaration(node);
  }

  @override
  void visitClassTypeAlias(ClassTypeAlias node) {
    // TODO(brianwilkerson): Update the interface to expose the token.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.sealedKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(
      node.interfaceKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.finalKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.BUILT_IN);
    super.visitClassTypeAlias(node);
  }

  @override
  void visitConstantPattern(ConstantPattern node) {
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitConstantPattern(node);
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.factoryKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.name,
      HighlightRegionType.CONSTRUCTOR,
      semanticTokenType: SemanticTokenTypes.method,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        SemanticTokenModifiers.declaration,
      },
    );
    super.visitConstructorDeclaration(node);
  }

  @override
  void visitConstructorReference(ConstructorReference node) {
    var constructorName = node.constructorName;
    constructorName.type.accept(this);

    // We have a `ConstructorReference` only when it is resolved.
    // TODO(scheglov): The `ConstructorName` in a tear-off always has a name,
    //  but this is not expressed via types.
    computer._addRegion_node(
      constructorName.name!,
      HighlightRegionType.CONSTRUCTOR_TEAR_OFF,
    );
  }

  @override
  void visitConstructorSelector(ConstructorSelector node) {
    computer._addRegion_node(node.name, HighlightRegionType.CONSTRUCTOR);
    node.visitChildren(this);
  }

  @override
  void visitContinueStatement(ContinueStatement node) {
    computer._addRegion_token(
      node.continueKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitContinueStatement(node);
  }

  @override
  void visitDeclaredIdentifier(DeclaredIdentifier node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredElement2,
      ),
    );

    super.visitDeclaredIdentifier(node);
  }

  @override
  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
    );
    super.visitDeclaredVariablePattern(node);
  }

  @override
  void visitDefaultFormalParameter(DefaultFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitDefaultFormalParameter(node);
  }

  @override
  void visitDoStatement(DoStatement node) {
    computer._addRegion_token(
      node.doKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.whileKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitDoStatement(node);
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_DOUBLE);
    super.visitDoubleLiteral(node);
  }

  @override
  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
    computer._addRegion_token(node.name, HighlightRegionType.ENUM_CONSTANT);
    node.visitChildren(this);
  }

  @override
  void visitEnumDeclaration(EnumDeclaration node) {
    // TODO(brianwilkerson): Uncomment the following lines when the token is
    //  supported.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.enumKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.name, HighlightRegionType.ENUM);
    super.visitEnumDeclaration(node);
  }

  @override
  void visitExportDirective(ExportDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.exportKeyword, HighlightRegionType.BUILT_IN);
    _addRegions_configurations(node.configurations);
    super.visitExportDirective(node);
  }

  @override
  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
    _addRegions_functionBody(node);
    super.visitExpressionFunctionBody(node);
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    computer._addRegion_token(node.extendsKeyword, HighlightRegionType.KEYWORD);
    super.visitExtendsClause(node);
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.extensionKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.name, HighlightRegionType.EXTENSION);
    super.visitExtensionDeclaration(node);
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    computer._addRegion_token(node.onKeyword, HighlightRegionType.BUILT_IN);
    super.visitExtensionOnClause(node);
  }

  @override
  void visitExtensionOverride(ExtensionOverride node) {
    computer._addRegion_token(node.name, HighlightRegionType.EXTENSION);

    super.visitExtensionOverride(node);
  }

  @override
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
    computer._addRegion_token(
      node.extensionKeyword,
      HighlightRegionType.BUILT_IN,
    );

    computer._addRegion_token(node.typeKeyword, HighlightRegionType.BUILT_IN);

    computer._addRegion_token(node.constKeyword, HighlightRegionType.BUILT_IN);

    computer._addRegion_token(
      node.name,
      HighlightRegionType.EXTENSION_TYPE,
      semanticTokenModifiers: {SemanticTokenModifiers.declaration},
    );

    super.visitExtensionTypeDeclaration(node);
  }

  @override
  void visitFieldDeclaration(FieldDeclaration node) {
    // TODO(brianwilkerson): Update the interface to expose the token.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.staticKeyword, HighlightRegionType.BUILT_IN);

    super.visitFieldDeclaration(node);
  }

  @override
  void visitFieldFormalParameter(FieldFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(node.thisKeyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      HighlightRegionType.INSTANCE_FIELD_REFERENCE,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredFragment?.element,
      ),
    );

    super.visitFieldFormalParameter(node);
  }

  @override
  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
    computer._addRegion_token(
      node.inKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForEachPartsWithDeclaration(node);
  }

  @override
  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
    computer._addRegion_token(
      node.inKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForEachPartsWithIdentifier(node);
  }

  @override
  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitForEachPartsWithPattern(node);
  }

  @override
  void visitForElement(ForElement node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.BUILT_IN,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.forKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForElement(node);
  }

  @override
  void visitForStatement(ForStatement node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.BUILT_IN,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.forKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForStatement(node);
  }

  @override
  void visitFunctionDeclaration(FunctionDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.propertyKeyword,
      HighlightRegionType.BUILT_IN,
    );

    HighlightRegionType nameType;
    if (node.isGetter) {
      nameType = HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION;
    } else if (node.isSetter) {
      nameType = HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION;
    } else if (node.parent is CompilationUnit) {
      nameType = HighlightRegionType.TOP_LEVEL_FUNCTION_DECLARATION;
    } else {
      nameType = HighlightRegionType.LOCAL_FUNCTION_DECLARATION;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitFunctionDeclaration(node);
  }

  @override
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
    computer._addRegion_token(
      node.typedefKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.name,
      HighlightRegionType.FUNCTION_TYPE_ALIAS,
    );
    super.visitFunctionTypeAlias(node);
  }

  @override
  void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.name,
      HighlightRegionType.PARAMETER_DECLARATION,
    );
    super.visitFunctionTypedFormalParameter(node);
  }

  @override
  void visitGenericFunctionType(GenericFunctionType node) {
    computer._addRegion_token(
      node.functionKeyword,
      HighlightRegionType.BUILT_IN,
    );
    super.visitGenericFunctionType(node);
  }

  @override
  void visitGenericTypeAlias(GenericTypeAlias node) {
    computer._addRegion_token(
      node.typedefKeyword,
      HighlightRegionType.BUILT_IN,
    );

    HighlightRegionType nameType;
    if (node.functionType != null) {
      nameType = HighlightRegionType.FUNCTION_TYPE_ALIAS;
    } else {
      nameType = HighlightRegionType.TYPE_ALIAS;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitGenericTypeAlias(node);
  }

  @override
  void visitHideCombinator(HideCombinator node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.BUILT_IN);
    super.visitHideCombinator(node);
  }

  @override
  void visitIfElement(IfElement node) {
    computer._addRegion_token(
      node.ifKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.elseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitIfElement(node);
  }

  @override
  void visitIfStatement(IfStatement node) {
    computer._addRegion_token(
      node.ifKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.elseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitIfStatement(node);
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    computer._addRegion_token(
      node.implementsKeyword,
      HighlightRegionType.BUILT_IN,
    );
    super.visitImplementsClause(node);
  }

  @override
  void visitImportDirective(ImportDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.importKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(
      node.deferredKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.asKeyword, HighlightRegionType.BUILT_IN);
    _addRegions_configurations(node.configurations);
    super.visitImportDirective(node);
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    computer._addRegion_token(node.name, HighlightRegionType.IMPORT_PREFIX);
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    if (node.keyword != null) {
      computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    }
    super.visitInstanceCreationExpression(node);
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_INTEGER);
    super.visitIntegerLiteral(node);
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    if (computer._computeSemanticTokens) {
      // Interpolation expressions may include uncolored code, but clients may
      // be providing their own basic coloring for strings that would leak
      // into those uncolored parts so we mark them up to allow the client to
      // reset the coloring if required.
      //
      // Using the String token type with a modifier would work for VS Code but
      // would cause other editors that don't know about the modifier (and also
      // do not have their own local coloring) to color the tokens as a string,
      // which is exactly what we'd like to avoid).

      computer._addRegion_node(
        node,
        // The HighlightRegionType here is not used because of the
        // computer._computeSemanticTokens check above.
        HighlightRegionType.LITERAL_STRING,
        semanticTokenType: CustomSemanticTokenTypes.source,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.interpolation},
      );
    }
    super.visitInterpolationExpression(node);
  }

  @override
  void visitInterpolationString(InterpolationString node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_STRING);
    super.visitInterpolationString(node);
  }

  @override
  void visitIsExpression(IsExpression node) {
    computer._addRegion_token(node.isOperator, HighlightRegionType.KEYWORD);
    super.visitIsExpression(node);
  }

  @override
  void visitLibraryDirective(LibraryDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(
      node.libraryKeyword,
      HighlightRegionType.BUILT_IN,
    );
    super.visitLibraryDirective(node);
  }

  @override
  void visitLibraryIdentifier(LibraryIdentifier node) {
    computer._addRegion_node(node, HighlightRegionType.LIBRARY_NAME);
    null;
  }

  @override
  void visitListLiteral(ListLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_LIST);
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitListLiteral(node);
  }

  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.modifierKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.operatorKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.propertyKeyword,
      HighlightRegionType.BUILT_IN,
    );

    HighlightRegionType nameType;
    if (node.isGetter) {
      nameType =
          node.isStatic
              ? HighlightRegionType.STATIC_GETTER_DECLARATION
              : HighlightRegionType.INSTANCE_GETTER_DECLARATION;
    } else if (node.isSetter) {
      nameType =
          node.isStatic
              ? HighlightRegionType.STATIC_SETTER_DECLARATION
              : HighlightRegionType.INSTANCE_SETTER_DECLARATION;
    } else {
      nameType =
          node.isStatic
              ? HighlightRegionType.STATIC_METHOD_DECLARATION
              : HighlightRegionType.INSTANCE_METHOD_DECLARATION;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitMethodDeclaration(node);
  }

  @override
  void visitMixinDeclaration(MixinDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.BUILT_IN);
    computer._addRegion_token(node.name, HighlightRegionType.MIXIN);
    super.visitMixinDeclaration(node);
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    computer._addRegion_token(node.onKeyword, HighlightRegionType.BUILT_IN);
    super.visitMixinOnClause(node);
  }

  @override
  void visitNamedType(NamedType node) {
    if (node.importPrefix case var importPrefix?) {
      computer._addRegion_token(
        importPrefix.name,
        HighlightRegionType.IMPORT_PREFIX,
      );
    }

    var type = node.type;
    if (type != null) {
      var isDynamic = type is DynamicType && node.name2.lexeme == 'dynamic';
      var isNever = type is NeverType;
      if (isDynamic || isNever) {
        computer._addRegion_token(
          node.name2,
          isDynamic
              ? HighlightRegionType.TYPE_NAME_DYNAMIC
              : HighlightRegionType.CLASS,
          semanticTokenType: SemanticTokenTypes.type,
        );
        return;
      }
    }

    computer._addIdentifierRegion(
      parent: node,
      nameToken: node.name2,
      element: node.element2,
    );

    node.typeArguments?.accept(this);
  }

  @override
  void visitNativeClause(NativeClause node) {
    computer._addRegion_token(node.nativeKeyword, HighlightRegionType.BUILT_IN);
    super.visitNativeClause(node);
  }

  @override
  void visitNativeFunctionBody(NativeFunctionBody node) {
    computer._addRegion_token(node.nativeKeyword, HighlightRegionType.BUILT_IN);
    super.visitNativeFunctionBody(node);
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    computer._addRegion_token(node.literal, HighlightRegionType.KEYWORD);
    super.visitNullLiteral(node);
  }

  @override
  void visitPartDirective(PartDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.partKeyword, HighlightRegionType.BUILT_IN);
    super.visitPartDirective(node);
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_tokenStart_tokenEnd(
      node.partKeyword,
      node.ofKeyword,
      HighlightRegionType.BUILT_IN,
    );
    super.visitPartOfDirective(node);
  }

  @override
  void visitPatternField(PatternField node) {
    var name = node.name?.name;
    if (name != null) {
      // Patterns can be method tear-offs as well as getters:
      // https://github.com/dart-lang/sdk/issues/59976#issuecomment-2613558317
      var type = switch (node.element2) {
        MethodElement2() => HighlightRegionType.INSTANCE_METHOD_TEAR_OFF,
        _ => HighlightRegionType.INSTANCE_GETTER_REFERENCE,
      };

      computer._addRegion_token(name, type);
    }
    super.visitPatternField(node);
  }

  @override
  void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitPatternVariableDeclaration(node);
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_RECORD);
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);

    for (var field in node.fields) {
      if (field is NamedExpression) {
        computer._addRegion_token(
          field.name.label.token,
          HighlightRegionType.PARAMETER_REFERENCE,
        );
        field.expression.accept(this);
      } else {
        field.accept(this);
      }
    }
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    for (var field in node.fields) {
      computer._addRegion_token(field.name, HighlightRegionType.FIELD);
    }

    super.visitRecordTypeAnnotation(node);
  }

  @override
  void visitRepresentationConstructorName(RepresentationConstructorName node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.CONSTRUCTOR,
      semanticTokenType: SemanticTokenTypes.method,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        SemanticTokenModifiers.declaration,
      },
    );

    super.visitRepresentationConstructorName(node);
  }

  @override
  void visitRepresentationDeclaration(RepresentationDeclaration node) {
    computer._addRegion_token(
      node.fieldName,
      HighlightRegionType.INSTANCE_FIELD_DECLARATION,
    );

    super.visitRepresentationDeclaration(node);
  }

  @override
  void visitRethrowExpression(RethrowExpression node) {
    computer._addRegion_token(
      node.rethrowKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitRethrowExpression(node);
  }

  @override
  void visitReturnStatement(ReturnStatement node) {
    computer._addRegion_token(
      node.returnKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitReturnStatement(node);
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    if (node.isMap) {
      computer._addRegion_node(node, HighlightRegionType.LITERAL_MAP);
      // TODO(brianwilkerson): Add a highlight region for set literals. This
      //  would be a breaking change, but would be consistent with list and map
      //  literals.
      //    } else if (node.isSet) {
      //    computer._addRegion_node(node, HighlightRegionType.LITERAL_SET);
    }
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitSetOrMapLiteral(node);
  }

  @override
  void visitShowCombinator(ShowCombinator node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.BUILT_IN);
    super.visitShowCombinator(node);
  }

  @override
  void visitSimpleFormalParameter(SimpleFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    var declaredElement = node.declaredFragment!.element;
    computer._addRegion_token(
      node.name,
      declaredElement.type is DynamicType
          ? HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION
          : HighlightRegionType.PARAMETER_DECLARATION,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        declaredElement,
      ),
    );

    super.visitSimpleFormalParameter(node);
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    var parent = node.parent;
    if (parent != null) {
      computer._addIdentifierRegion(
        parent: parent,
        nameToken: node.token,
        element: node.writeOrReadElement2,
      );
    }
    super.visitSimpleIdentifier(node);
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_STRING);
    if (computer._computeSemanticTokens) {
      _addRegions_stringEscapes(node);
    }
    super.visitSimpleStringLiteral(node);
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);
    super.visitSuperConstructorInvocation(node);
  }

  @override
  void visitSuperExpression(SuperExpression node) {
    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);
    super.visitSuperExpression(node);
  }

  @override
  void visitSuperFormalParameter(SuperFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      node.declaredFragment?.element.type is DynamicType
          ? HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION
          : HighlightRegionType.PARAMETER_DECLARATION,
    );

    super.visitSuperFormalParameter(node);
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchCase(node);
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchDefault(node);
  }

  @override
  void visitSwitchExpression(SwitchExpression node) {
    computer._addRegion_token(node.switchKeyword, HighlightRegionType.KEYWORD);
    super.visitSwitchExpression(node);
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchPatternCase(node);
  }

  @override
  void visitSwitchStatement(SwitchStatement node) {
    computer._addRegion_token(
      node.switchKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchStatement(node);
  }

  @override
  void visitThisExpression(ThisExpression node) {
    computer._addRegion_token(node.thisKeyword, HighlightRegionType.KEYWORD);
    super.visitThisExpression(node);
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    computer._addRegion_token(
      node.throwKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitThrowExpression(node);
  }

  @override
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
    computer._addRegion_token(
      node.augmentKeyword,
      HighlightRegionType.BUILT_IN,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.BUILT_IN,
    );

    super.visitTopLevelVariableDeclaration(node);
  }

  @override
  void visitTryStatement(TryStatement node) {
    computer._addRegion_token(
      node.tryKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.finallyKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitTryStatement(node);
  }

  @override
  void visitTypeParameter(TypeParameter node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.TYPE_PARAMETER,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredFragment?.element,
      ),
    );
    super.visitTypeParameter(node);
  }

  @override
  void visitVariableDeclaration(VariableDeclaration node) {
    var element = node.declaredFragment?.element ?? node.declaredElement2;
    if (element is FieldElement2) {
      computer._addRegion_token(
        node.name,
        element.isStatic
            ? HighlightRegionType.STATIC_FIELD_DECLARATION
            : HighlightRegionType.INSTANCE_FIELD_DECLARATION,
      );
    } else if (element is LocalVariableElement2) {
      computer._addRegion_token(
        node.name,
        element.type is DynamicType
            ? HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_DECLARATION
            : HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          element,
        ),
      );
    } else if (element is TopLevelVariableElement2) {
      computer._addRegion_token(
        node.name,
        HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION,
      );
    }

    super.visitVariableDeclaration(node);
  }

  @override
  void visitVariableDeclarationList(VariableDeclarationList node) {
    computer._addRegion_token(node.lateKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitVariableDeclarationList(node);
  }

  @override
  void visitWhenClause(WhenClause node) {
    computer._addRegion_token(
      node.whenKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitWhenClause(node);
  }

  @override
  void visitWhileStatement(WhileStatement node) {
    computer._addRegion_token(
      node.whileKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitWhileStatement(node);
  }

  @override
  void visitWildcardPattern(WildcardPattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);

    // For consistency, wildcard patterns are treated like wildcard variable
    // declarations.
    // https://github.com/dart-lang/sdk/issues/56567
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
      additionalSemanticTokenModifiers: {CustomSemanticTokenModifiers.wildcard},
    );

    super.visitWildcardPattern(node);
  }

  @override
  void visitWithClause(WithClause node) {
    computer._addRegion_token(node.withKeyword, HighlightRegionType.KEYWORD);
    super.visitWithClause(node);
  }

  @override
  void visitYieldStatement(YieldStatement node) {
    var keyword = node.yieldKeyword;
    var star = node.star;
    var offset = keyword.offset;
    var end = star != null ? star.end : keyword.end;
    computer._addRegion(
      offset,
      end - offset,
      HighlightRegionType.BUILT_IN,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitYieldStatement(node);
  }

  /// Returns a set of additional semantic token modifiers that apply to
  /// [element].
  Set<SemanticTokenModifiers>? _additionalModifiersForElement(
    Element2? element,
  ) {
    return computer._additionalModifiersForElement(element);
  }

  void _addRegions_configurations(List<Configuration> configurations) {
    for (var configuration in configurations) {
      computer._addRegion_token(
        configuration.ifKeyword,
        HighlightRegionType.BUILT_IN,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
      );
    }
  }

  void _addRegions_functionBody(FunctionBody node) {
    var keyword = node.keyword;
    if (keyword != null) {
      var star = node.star;
      var offset = keyword.offset;
      var end = star != null ? star.end : keyword.end;
      computer._addRegion(
        offset,
        end - offset,
        HighlightRegionType.BUILT_IN,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
      );
    }
  }

  void _addRegions_stringEscapes(SimpleStringLiteral node) {
    var string = node.literal.lexeme;
    var quote = analyzeQuote(string);
    var startIndex = firstQuoteLength(string, quote);
    var endIndex = string.length - lastQuoteLength(quote);
    switch (quote) {
      case Quote.Single:
      case Quote.Double:
      case Quote.MultiLineSingle:
      case Quote.MultiLineDouble:
        _findEscapes(
          node,
          startIndex: startIndex,
          endIndex: endIndex,
          listener: (offset, end) {
            var length = end - offset;
            computer._addRegion(
              node.offset + offset,
              length,
              HighlightRegionType.VALID_STRING_ESCAPE,
            );
          },
        );
      case Quote.RawSingle:
      case Quote.RawDouble:
      case Quote.RawMultiLineSingle:
      case Quote.RawMultiLineDouble:
        // Raw strings don't have escape characters.
        break;
    }
  }

  /// Finds escaped regions within a string between [startIndex] and [endIndex],
  /// calling [listener] for each found region.
  void _findEscapes(
    SimpleStringLiteral node, {
    required int startIndex,
    required int endIndex,
    required void Function(int offset, int end) listener,
  }) {
    var string = node.literal.lexeme;
    var codeUnits = string.codeUnits;
    var length = string.length;

    bool isBackslash(int i) => i <= length && codeUnits[i] == char.$BACKSLASH;
    bool isHexEscape(int i) => i <= length && codeUnits[i] == char.$x;
    bool isUnicodeHexEscape(int i) => i <= length && codeUnits[i] == char.$u;
    bool isOpenBrace(int i) =>
        i <= length && codeUnits[i] == char.$OPEN_CURLY_BRACKET;
    bool isCloseBrace(int i) =>
        i <= length && codeUnits[i] == char.$CLOSE_CURLY_BRACKET;
    int? numHexDigits(int i, {required int min, required int max}) {
      var numHexDigits = 0;
      for (var j = i; j < math.min(i + max, length); j++) {
        if (!char.isHexDigit(codeUnits[j])) {
          break;
        }
        numHexDigits++;
      }
      return numHexDigits >= min ? numHexDigits : null;
    }

    for (var i = startIndex; i < endIndex;) {
      if (isBackslash(i)) {
        var backslashOffset = i++;
        // All escaped characters are a single character except for:
        // `\uXXXX` or `\u{XX?X?X?X?X?}` for Unicode hex escape.
        // `\xXX` for hex escape.
        if (isHexEscape(i)) {
          // Expect exactly 2 hex digits.
          var numDigits = numHexDigits(i + 1, min: 2, max: 2);
          if (numDigits != null) {
            i += 1 + numDigits;
            listener(backslashOffset, i);
          }
        } else if (isUnicodeHexEscape(i) && isOpenBrace(i + 1)) {
          // Expect 1-6 hex digits followed by '}'.
          var numDigits = numHexDigits(i + 2, min: 1, max: 6);
          if (numDigits != null && isCloseBrace(i + 2 + numDigits)) {
            i += 2 + numDigits + 1;
            listener(backslashOffset, i);
          }
        } else if (isUnicodeHexEscape(i)) {
          // Expect exactly 4 hex digits.
          var numDigits = numHexDigits(i + 1, min: 4, max: 4);
          if (numDigits != null) {
            i += 1 + numDigits;
            listener(backslashOffset, i);
          }
        } else {
          i++;
          // Single-character escape.
          listener(backslashOffset, i);
        }
      } else {
        i++;
      }
    }
  }
}
